package org.xx.armory.spring5.jdbc;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 用于构建SQL命令字符串的工具类。
 * <p>该工具类内部使用列表保存多个字符串，然后再拼接，这样比使用类似{@code sql = sql + " WHERE COL1 = 2"}的代码更加高效。</p>
 */
public final class SqlStringBuilder {
    private static final int DEFAULT_SEGMENTS_CAPACITY = 16;

    /**
     * 默认的连接符号是一个空格。
     */
    private static final String DEFAULT_JOINT = " ";

    private final List<String> segments;

    private final String prefix;
    private final String joint;
    private final String suffix;

    /**
     * 构造工具类的实例，使用默认的连接符号。
     */
    public SqlStringBuilder() {
        this("", DEFAULT_JOINT, "");
    }

    /**
     * 构造工具类的实例，并指定连接符号。
     *
     * @param prefix
     *         前缀内容，如果最终结果不是空字符串，那么自动附加前缀。
     * @param joint
     *         连接符号。
     * @param suffix
     *         后缀内容，如果最终结果不是空字符串，那么自动附加后缀。
     * @throws IllegalArgumentException
     *         如果参数 {@code prefix} 或者 {@code joint} 或者 {@code suffix} 是 {@code null}。
     */
    public SqlStringBuilder(
            String prefix,
            String joint,
            String suffix
    ) {
        this.prefix = rejectIfNull(prefix, "prefix");
        this.joint = rejectIfNull(joint, "joint");
        this.suffix = rejectIfNull(suffix, "suffix");

        this.segments = new ArrayList<>(DEFAULT_SEGMENTS_CAPACITY);
    }

    /**
     * 清除所有已保存的字符串。
     */
    public final void clear() {
        this.segments.clear();
    }

    /**
     * 在最前方插入新的字符串。
     *
     * @param s
     *         待添加的字符串，如果此参数是{@code null}或者空字符串则不添加，保留首尾空格。
     * @return 此工具类实例自身。
     */
    public final SqlStringBuilder prepend(
            String s
    ) {
        if (s != null && !s.isEmpty()) {
            segments.add(0, s);
        }

        return this;
    }

    /**
     * 在末尾追加一个新的字符串。
     *
     * @param s
     *         待添加的字符串，如果此参数是{@code null}或者空字符串则不添加，保留首尾空格。
     * @return 此工具类实例自身。
     */
    public final SqlStringBuilder append(
            String s
    ) {
        if (s != null && !s.isEmpty()) {
            segments.add(s);
        }

        return this;
    }

    /**
     * 判断待拼接的字符串列表是否为空。
     *
     * @return 如果不存在待拼接的字符串则返回 {@code true}，否则返回{@code false}。
     */
    public final boolean isEmpty() {
        return this.segments.isEmpty();
    }

    /**
     * 构造目标字符串。
     * <p>目标字符串由当前添加的所有字符串依次连接组成，每个字符串之间用{@code joint}字段分隔。</p>
     *
     * @return 目标字符串。
     */
    public final String build() {
        if (this.segments.size() == 0) {
            return "";
        }

        Iterator<String> iterator;

        // 计算缓冲区的总长度。
        var totalLength = 0;
        totalLength += prefix.length();
        iterator = segments.iterator();
        totalLength += iterator.next().length();
        while (iterator.hasNext()) {
            totalLength += this.joint.length(); // 增加一个连接符号的长度。
            totalLength += iterator.next().length();
        }
        totalLength += suffix.length();

        // 将每个部分逐个加入缓冲区。
        final var buffer = new StringBuilder(totalLength);
        buffer.append(prefix);
        iterator = segments.iterator();
        buffer.append(iterator.next());
        while (iterator.hasNext()) {
            buffer.append(this.joint).append(iterator.next());
        }
        buffer.append(suffix);

        return buffer.toString();
    }
}

